/*
 * Copyright (c) 2022, yifengling0. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. No personal names or organizations' names associated with the
 *    Atomthreads project may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "atom.h"
#include "iosdrv.h"
#include "drv_rtc.h"
#include "stm8l15x_clk.h"
#include "stm8l15x_rtc.h"
#include <string.h>

static RtcWakeAlarm _alarm = {0, 0, 0, 0, NULL};
static RtcWakeUp _wakeup = {0, RTCCLK_Div16, 0, NULL};

static void get_rtc_timestamp(RtcTime *tm_new)
{
    RTC_TimeTypeDef RTC_TimeStruct;
    RTC_DateTypeDef RTC_DateStruct;

    RTC_GetTime(RTC_Format_BIN, &RTC_TimeStruct );
    RTC_GetDate(RTC_Format_BIN, &RTC_DateStruct );

    tm_new->tm_sec  = RTC_TimeStruct.RTC_Seconds;
    tm_new->tm_min  = RTC_TimeStruct.RTC_Minutes;
    tm_new->tm_hour = RTC_TimeStruct.RTC_Hours;
    tm_new->tm_mday = RTC_DateStruct.RTC_Date;
    tm_new->tm_mon  = RTC_DateStruct.RTC_Month - 1;
    tm_new->tm_year = RTC_DateStruct.RTC_Year + 100;
}

static int set_rtc_time_stamp(RtcTime *time_stamp)
{
    RTC_TimeTypeDef RTC_TimeStruct;
    RTC_DateTypeDef RTC_DateStruct;
    RtcTime *p_tm;

    p_tm = time_stamp;
    if (p_tm->tm_year < 100)
    {
        return NG;
    }
    
    RTC_TimeStructInit(&RTC_TimeStruct);
    RTC_DateStructInit(&RTC_DateStruct);

    RTC_TimeStruct.RTC_Seconds = p_tm->tm_sec ;
    RTC_TimeStruct.RTC_Minutes = p_tm->tm_min ;
    RTC_TimeStruct.RTC_Hours   = p_tm->tm_hour;
    RTC_DateStruct.RTC_Date    = p_tm->tm_mday;
    RTC_DateStruct.RTC_Month   = (RTC_Month_TypeDef)(p_tm->tm_mon + 1);
    RTC_DateStruct.RTC_Year    = p_tm->tm_year - 100;
    RTC_DateStruct.RTC_WeekDay = (RTC_Weekday_TypeDef)(p_tm->tm_wday + 1);

    if (RTC_SetTime(RTC_Format_BIN, &RTC_TimeStruct ) != SUCCESS)
    {
        return NG;
    }
    if (RTC_SetDate(RTC_Format_BIN, &RTC_DateStruct ) != SUCCESS)
    {
        return NG;
    }

    return OK;
}


static void set_rtc_alarm(RtcWakeAlarm *alarm)
{
    RTC_AlarmTypeDef RTC_AlarmStruct;

    RTC_AlarmStruct.RTC_AlarmTime.RTC_Hours   = alarm->tm_hour;
    RTC_AlarmStruct.RTC_AlarmTime.RTC_Minutes = alarm->tm_min;
    RTC_AlarmStruct.RTC_AlarmTime.RTC_Seconds = alarm->tm_sec; 
    RTC_AlarmStruct.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay; // 意思是屏蔽掉日期和星期，那么就只有小时、分钟、秒了

    memcpy(&_alarm, alarm, sizeof(RtcWakeAlarm));
    RTC_SetAlarm(RTC_Format_BIN, &RTC_AlarmStruct);
    RTC_ITConfig(RTC_IT_ALRA, (FunctionalState)alarm->enable);
    RTC_AlarmCmd((FunctionalState)alarm->enable);
}

static void get_rtc_alarm(RtcWakeAlarm *alarm)
{
    RTC_AlarmTypeDef RTC_AlarmStruct;
    RTC_GetAlarm(RTC_Format_BIN, &RTC_AlarmStruct);
    alarm->tm_hour = RTC_AlarmStruct.RTC_AlarmTime.RTC_Hours;
    alarm->tm_min  = RTC_AlarmStruct.RTC_AlarmTime.RTC_Minutes;
    alarm->tm_sec  = RTC_AlarmStruct.RTC_AlarmTime.RTC_Seconds;
}

static void set_rtc_wakeup(RtcWakeUp *wakeup)
{
    memcpy(&_wakeup, wakeup, sizeof(RtcWakeUp));

    if( wakeup->enable )
    {
        RTC_WakeUpClockConfig((RTC_WakeUpClock_TypeDef)wakeup->clock);
        RTC_SetWakeUpCounter(wakeup->counter);
        RTC_ITConfig(RTC_IT_WUT, ENABLE);
        RTC_WakeUpCmd(ENABLE);
    }
    else
    {
        RTC_WakeUpCmd(DISABLE);
        RTC_ITConfig(RTC_IT_WUT, DISABLE);
    }
}

static int rtc_ioctl(FD fd, int function, void* arg)
{
    switch (function)
    {
    case RTC_CTRL_GET_DATETIME:
        get_rtc_timestamp((RtcTime*)arg);
        break;
    case RTC_CTRL_SET_DATETIME:
        if (set_rtc_time_stamp((RtcTime *)arg))
        {
            return NG;
        }
        break;
    case RTC_CTRL_GET_ALARM:
        set_rtc_alarm((RtcWakeAlarm *)arg);
        break;
    case RTC_CTRL_SET_ALARM:
        get_rtc_alarm((RtcWakeAlarm *)arg);
        break;
    case RTC_CTRL_SET_WAKEUP:
        set_rtc_wakeup((RtcWakeUp *)arg);
        break;
    }

    return OK;
}

static void rtc_init(void)
{
    RTC_InitTypeDef rtcDef;
    CLK_LSEConfig(CLK_LSE_ON);
    while (CLK_GetFlagStatus(CLK_FLAG_LSERDY) == RESET);
    CLK_RTCClockConfig(CLK_RTCCLKSource_LSE, CLK_RTCCLKDiv_1);
    CLK_PeripheralClockConfig(CLK_Peripheral_RTC, ENABLE);
    
    rtcDef.RTC_HourFormat = RTC_HourFormat_24;
    rtcDef.RTC_AsynchPrediv = 0x7F;
    rtcDef.RTC_SynchPrediv = 0x00FF;
    
    RTC_Init(&rtcDef);
}

void RTC_LSE_IRQHandler()
{
    atomIntEnter();
    if( RTC_GetITStatus(RTC_IT_ALRA) == SET ){
        RTC_ClearITPendingBit(RTC_IT_ALRA);
        if( _alarm.enable){
            if(_alarm.callback){
                _alarm.callback(&_alarm);
            }
        }
    }else if( RTC_GetITStatus(RTC_IT_WUT) == SET ){
        RTC_ClearITPendingBit(RTC_IT_WUT);
        set_rtc_wakeup(&_wakeup); //重置counter保证周期
        if( _wakeup.enable){
            if(_wakeup.callback){
                _wakeup.callback(&_wakeup);
            }
        }
    }
    atomIntExit(FALSE);
}

void Stm8lRtcCreate(Stm8lRtc *rtc)
{
    RtcCreate(&rtc->Super, rtc_ioctl);
    rtc_init();
}
